﻿using System;
using System.IO;
using System.Globalization;
using NumericalMatrix.Exceptions;
using NumericalMatrix.Interfaces;
using NumericalMatrix.Services;

namespace NumericalMatrix
{
    class Matrix2 //: IMatrix<double>
    {
        #region Properties

        private double[,] m;
        public double[,] M
        {
            get { return m; }
            set { m = value; }
        }

        private int width;
        public int Width
        {
            get { return width; }
        }

        private int height;
        public int Height
        {
            get { return height; }
        }

        private NumberDecimalSeparator numberDecimalSeparator = NumberDecimalSeparator.Dot;
        public NumberDecimalSeparator NumberDecimalSeparator
        {
            get { return numberDecimalSeparator; }
        }

        private double quality = 1e-10;
        public double Quality
        {
            get { return quality; }
            set { quality = value; }
        }

        private NumberFormatInfo numberFormatInfo;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor without parameters
        /// </summary>
        public Matrix2()
        {
            M = new double[0, 0];
            SetDotNumberSeparatorFormat();
        }

        /// <summary>
        /// Create instance of <see cref="Matrix2"/> from 2D array
        /// </summary>
        /// <param name="array">
        /// The array
        /// </param>
        public Matrix2(double[,] array)
        {
            height = array.GetLength(0);
            width = array.GetLength(1);

            m = new double[height, width];

            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++)
                    if (Math.Abs(array[i, j]) > Quality)
                        m[i, j] = array[i, j];

            SetDotNumberSeparatorFormat();
        }

        /// <summary>
        /// Create zero instance of <see cref="Matrix2"/> of entered size
        /// </summary>
        /// <param name="height">number of rows</param>
        /// <param name="width">number of column</param>
        public Matrix2(int height, int width)
        {
            this.width = width;
            this.height = height;

            m = new double[this.width, this.height];
            SetDotNumberSeparatorFormat();
        }

        /// <summary>
        /// Crates instance of <see cref="Matrix2"/> from file of special format
        /// </summary>
        /// <param name="s"></param>
        public Matrix2(string s)
        {
            using (var str = new StreamReader(s))
            {

                string[] ns = (str.ReadLine()).Split();

                width = int.Parse(ns[1]);
                height = int.Parse(ns[0]);

                m = new double[height, width];

                for (int i = 0; i < height; i++)
                {
                    string[] line = (str.ReadLine()).Split();
                    for (int j = 0; j < width; j++)
                        m[i, j] = double.Parse(line[j]);
                }
            }
            SetDotNumberSeparatorFormat();
        }

        /// <summary>
        /// Crates instance of <see cref="Matrix2"/> from other <see cref="Matrix2"/>
        /// </summary>
        /// <param name="A"></param>
        public Matrix2(Matrix2 A)
        {
            width = A.width;
            height = A.height;

            m = new double[height, width];

            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                    if (Math.Abs(A[i, j]) > Quality)
                        m[i, j] = A[i, j];
            }
            SetDotNumberSeparatorFormat();
        }
        
        /// <summary>
        /// Crates Vector (Matrix with 1 column) from array
        /// </summary>
        /// <param name="A"></param>
        public Matrix2(double[] A)
        {
            width = 1;
            height = A.Length;

            m = new double[height, width];

            for (int i = 0; i < height; i++)
            {
                    m[i,0] = A[i];
            }
            SetDotNumberSeparatorFormat();
        }

        #endregion

        #region IMatrix interface implementation

        /// <summary>
        /// indexator
        /// </summary>
        /// <param name="i">
        /// row number
        /// </param>
        /// <param name="j">
        /// column number</param>
        /// <returns></returns>
        public double this[int i, int j]
        {
            set
            { m[i, j] = value; }
            get
            { return m[i, j]; }
        }

        #endregion

        #region Public methods

        #region General methods

        /// <summary>
        /// Set dot number separator format
        /// </summary>
        public void SetDotNumberSeparatorFormat()
        {
            CultureInfo ci = CultureInfo.InstalledUICulture;
            numberFormatInfo = (NumberFormatInfo)ci.NumberFormat.Clone();
            numberFormatInfo.NumberDecimalSeparator = ".";
            numberDecimalSeparator = NumberDecimalSeparator.Dot;
        }

        /// <summary>
        /// Set comma number separator format
        /// </summary>
        public void SetCommaNumberSeparatorFormat()
        {
            CultureInfo ci = CultureInfo.InstalledUICulture;
            numberFormatInfo = (NumberFormatInfo)ci.NumberFormat.Clone();
            numberFormatInfo.NumberDecimalSeparator = ".";
            numberDecimalSeparator = NumberDecimalSeparator.Comma;
        }

        /// <summary>
        /// Writes <see cref="Matrix2"/> into the file
        /// </summary>
        /// <param name="fileName">
        /// specified file name
        /// </param>
        public void WriteToFile(string fileName)
        {
            using (var str = new StreamWriter(fileName))
            {
                str.WriteLine(height.ToString() + " " + width.ToString());

                for (int i = 0; i < height; i++)
                {
                    string line = "";
                    for (int j = 0; j < width; j++)
                        if (Math.Abs(m[i, j]) < Quality)
                            line += 0 + " ";
                        else
                            line += m[i, j].ToString(numberFormatInfo) + " ";
                    str.WriteLine(line);
                }
            }
        }

        /// <summary>
        /// Writes <see cref="Matrix2"/> into the file
        /// </summary>
        /// <param name="stream">
        /// specified stream writer
        /// </param>
        public void WriteToFile(StreamWriter stream)
        {
            stream.WriteLine(height.ToString() + " " + width.ToString());

            for (int i = 0; i < height; i++)
            {
                string line = "";
                for (int j = 0; j < width; j++)
                    if(Math.Abs(m[i,j])<Quality)
                    line += 0 + " ";
                else
                        line += m[i, j].ToString(numberFormatInfo) + " ";
                stream.WriteLine(line);
            }
        }

        public void Equal2This(IMatrix<double> a)
        {
            height = a.Height;
            width = a.Width;

            m = new double[height, width];

            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++)
                {
                    if (Math.Abs(a[i, j]) < Quality)
                        m[i, j] = 0;
                    else
                        m[i, j] = a[i, j];
                }
        }

        #endregion

        public Matrix2 AddRight(Matrix2 a)
        {
            if (height == a.height)
            {
                Matrix2 C = new Matrix2(height, width + a.width);
                for (int i = 0; i < height; i++)
                {
                    for (int j1 = 0; j1 < width; j1++)
                        C[i, j1] = m[i, j1];

                    for (int j2 = 0; j2 < a.width; j2++)
                        C[i, j2 + width] = a[i, j2];
                }

                return C;
            }
            throw new MatrixException("Wrong dimensions: row count of matrices must be equal.");
        }

        public Matrix2 AddDiag(Matrix2 a)
        {
            Matrix2 C = new Matrix2(height+a.height, width + a.width);

            for (int i = 0; i < height; i++)
            {
                for (int j1 = 0; j1 < width; j1++)
                    C[i, j1] = m[i, j1];
            }

            for (int i = 0; i < a.height; i++)
            {
                for (int j2 = 0; j2 < a.width; j2++)
                    C[i+height, j2 + width] = a[i, j2];
            }
            return C;
        }

        static public Matrix2 operator +(Matrix2 a, Matrix2 b)
        {
            if (a.height != b.height && a.width != b.width)
                throw new MatrixException("Matrices has different dimention.");

            Matrix2 C = new Matrix2(a.height, a.width);

            for (int i = 0; i < b.height; i++)
                for (int j = 0; j < b.width; j++)
                    C[i, j] = a[i, j] + b[i, j];

            return C;
        }

        static public Matrix2 operator -(Matrix2 a, Matrix2 b)  // зробити аналогічно і в додаванні
        {
            if (a.height != b.height && a.width != b.width)
                throw new MatrixException("Matrices has different dimention.");

            Matrix2 C = new Matrix2(a);

            for (int i = 0; i < b.height; i++)
                for (int j = 0; j < b.width; j++)
                    C[i, j] = a[i, j] - b[i, j];

            return C;
        }

        static public Matrix2 operator *(Matrix2 a, Matrix2 b)
        {
            Matrix2 C;
            if (a.width == b.height)
            {
                C = new Matrix2(a.height, b.width);

                for (int i = 0; i < a.height; i++)
                    for (int j = 0; j < b.width; j++)
                    {
                        double mult = 0;
                        for (int k = 0; k < a.width; k++)
                        {
                            mult += a[i, k] * b[k, j];
                        }
                        C[i, j] = mult;
                    }
                return C;
            }
            throw new MatrixException("Dimension mismatch: columns count of first martix are not equal to rows count of second matrix.");
        }

        static public Matrix2 operator *(double c, Matrix2 b)
        {
            Matrix2 C;
            C = new Matrix2(b.height, b.width);

            for (int i = 0; i < b.height; i++)
                for (int j = 0; j < b.width; j++)
                {
                    C[i, j] = b[i, j] * c;
                }
            return C;
        }

        public Matrix2 Transpose()
        {
            Matrix2 C = new Matrix2(width, height);

            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++)
                    C[j, i] = m[i, j];

            return C;
        }

        public Matrix2 Holetckyy()
        {
            Matrix2 U = new Matrix2(height, width);
            if (width == height)
            {
                for (int i = 0; i < height; i++)
                {
                    double sum = 0;
                    for (int k = 0; k < i; k++)
                        sum += U[k, i] * U[k, i];
                    
                    double d = m[i, i] - sum;

                    if (d < 0.000000001)          //мало би бути по модулю, але не нах. тоді залежних рядків;
                        d = 0;
                    U[i, i] = Math.Sqrt(d);

                    if (U[i, i] != 0)
                    {
                        for (int j = i + 1; j < width; j++)
                        {
                            sum = 0;
                            for (int k = 0; k < i; k++)
                                sum += U[k, i] * U[k, j];
                            U[i, j] = (m[i, j] - sum) / U[i, i];
                        }
                    }

                }

                return U;
            }
            throw new MatrixException("Matrix is not quadrad.");
        }

        //public Matrix[] LU()
        //{
        //    Matrix[] res = new Matrix[2];

        //    if (width == height)
        //    {
        //        Matrix L = new Matrix(width, height);
        //        Matrix U = new Matrix(width, height);

        //        for (int i = 0; i < width; i++)
        //            L[i, i] = 1;

        //        //iteration by L rows
        //        for (int i = 0; i < width; i++)
        //        {
        //            for (int j = 0; j < i; j++)
        //            {
        //                double s = 0;
        //                for (int k = 0; k < j; k++)
        //                    s += L[i, k] * U[k, j];
        //                L[i, j] = (M[i, j] - s) / U[j, j];
        //            }

        //            for (int j = i; j < width; j++)
        //            {
        //                double s = 0;
        //                for (int k = 0; k < i; k++)
        //                    s += L[i, k] * U[k, j];
        //                U[i, j] = M[i, j] - s;
        //            }
        //        }

        //        res[0] = L;
        //        res[1] = U;
        //    }
        //    else
        //        throw new MatrixException(" usage of non quadrad matrix: ");

        //    return res;
        //}

        public Matrix2 Gauss()
        {
            Matrix2 res = new Matrix2(this);

            if (width == height)
            {
                for (int i = 0; i < width; i++)
                {
                    for (int j = i + 1; j < width; j++)
                    {
                        if (res[i, i] == 0) break;
                        double s = res[j, i] / res[i, i];
                        for (int k = i; k < width; k++)
                        {
                            res[j, k] -= s * res[i, k];
                            if (Math.Abs(res[j, k]) < 1E-9) res[j, k] = 0;
                        }
                    }
                }
            }
            else
                throw new Exception(" usage of non quadrad matrix: ");

            return res;
        }

        public Matrix2 InvertHol()
        {
            if (width == height)
            {
                Matrix2 RH = new Matrix2(this.Holetckyy());
                Matrix2 LH = new Matrix2(RH.Transpose());


                Matrix2 S = new Matrix2(RH.Height, RH.Width);

                Matrix2 x = new Matrix2();
                Matrix2 y = new Matrix2();

                for (int i = 0; i < width; i++)
                {
                    Matrix2 b = new Matrix2(height, 1);
                    b[i, 0] = 1;

                    //y.Equal2This(LH.LinSolveLtriang(b));
                    //x.Equal2This(RH.LinSolveRtriang(y));
                    
                    for (int j = 0; j < width; j++)
                    {
                        S[j, i] = x[j, 0];
                    }
                }

                return S;
            }
            else
            { throw new Exception(" using not quadrad matrix"); }
        }

        //public Matrix InvertLU()
        //{
        //    Matrix S = new Matrix(height, width);
        //    if (width == height)
        //    {

        //        Matrix[] LU = this.LU();

        //        Matrix x;
        //        Matrix y;

        //        for (int i = 0; i < width; i++)
        //        {
        //            Matrix b = new Matrix(height, 1);
        //            b[i, 0] = 1;

        //            y = LU[0].LinSolveLtriang(b) as Matrix;
        //            //(LH * y).WriteToFile("LH_Y.txt");
        //            x = LU[1].LinSolveRtriang(y);
        //            //y.WriteToFile("y.txt");
        //            //(RH * x).WriteToFile("RH_X.txt");

        //            for (int j = 0; j < width; j++)
        //            {
        //                S[j, i] = x[j, 0];
        //            }

        //        }

        //    }
        //    else throw new Exception(" non quadrat matrix ");
        //    return S;
        //}



        #endregion

        //public IMatrix<double> CreateDoubleInstance(int height, int width)
        //{
        //    return new Matrix2(height, width);
        //}

        //public IMatrix<GroupOperators> CreateInstance(int height, int width)
        //{
        //    throw new NotImplementedException();
        //}

        //ILUBlock<double> lUBlock;
        //public ILUBlock<double> LUBlock
        //{
        //    get
        //    {
        //        if (lUBlock == null)
        //        {
        //            lUBlock = new LUBlock<double>(this);
        //        }
        //        return lUBlock;
        //    }
        //}
    }
}
